// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <pollux/common/base/pollux_exception.h>

namespace kumo {
    namespace pollux {
        namespace dwio {
            namespace common {
                namespace exception {
                    class ExceptionLogger {
                    public:
                        virtual ~ExceptionLogger() = default;

                        virtual void logException(
                            const char *file,
                            size_t line,
                            const char *function,
                            const char *expression,
                            const char *message) = 0;

                        virtual void logWarning(
                            const char *file,
                            size_t line,
                            const char *function,
                            const char *expression,
                            const char *message) = 0;
                    };

                    bool registerExceptionLogger(std::unique_ptr<ExceptionLogger> logger);

                    ExceptionLogger *getExceptionLogger();

                    class LoggedException : public pollux::PolluxException {
                    public:
                        explicit LoggedException(
                            const std::string &errorMessage,
                            const std::string &errorSource =
                                    ::kumo::pollux::error_source::kErrorSourceRuntime,
                            const std::string &errorCode = ::kumo::pollux::error_code::kUnknown,
                            const bool isRetriable = false)
                            : PolluxException(
                                nullptr,
                                0,
                                nullptr,
                                "",
                                errorMessage,
                                errorSource,
                                errorCode,
                                isRetriable) {
                            logException();
                        }

                        LoggedException(
                            const char *file,
                            size_t line,
                            const char *function,
                            const char *expression,
                            const std::string &errorMessage,
                            const std::string &errorSource,
                            const std::string &errorCode)
                            : PolluxException(
                                file,
                                line,
                                function,
                                expression,
                                errorMessage,
                                errorSource,
                                errorCode,
                                false) {
                            logException();
                        }

                    private:
                        void logException() {
                            auto logger = getExceptionLogger();
                            if (logger) {
                                logger->logException(
                                    file(),
                                    line(),
                                    function(),
                                    failingExpression().data(),
                                    PolluxException::what());
                            }
                        }
                    };
                } // namespace exception
            } // namespace common

#define DWIO_WARN_IF(e, ...)                                                \
  ({                                                                        \
    auto const& _tmp = (e);                                                 \
    if (_tmp) {                                                             \
      auto logger =                                                         \
          ::kumo::pollux::dwio::common::exception::getExceptionLogger(); \
      if (logger) {                                                         \
        logger->logWarning(                                                 \
            __FILE__,                                                       \
            __LINE__,                                                       \
            __FUNCTION__,                                                   \
            #e,                                                             \
            ::melon::to<std::string>(__VA_ARGS__).c_str());                 \
      }                                                                     \
    }                                                                       \
  })

#define DWIO_WARN(...) DWIO_WARN_IF(true, ##__VA_ARGS__)

#define DWIO_WARN_EVERY_N(n, ...)          \
  static size_t LOG_OCCURRENCES_MOD_N = 0; \
  if (++LOG_OCCURRENCES_MOD_N > n)         \
    LOG_OCCURRENCES_MOD_N -= n;            \
  if (LOG_OCCURRENCES_MOD_N == 1)          \
    DWIO_WARN(__VA_ARGS__);

            /*
             * Use ENFORCE(expr) or ENFORCE(expr, message) whenever you want to
             * make sure that expr is nonzero. If it is zero, an exception is
             * thrown. The type yielded by ENFORCE is the same as expr, so it acts
             * like an identity function, which makes it convenient to insert
             * anywhere. For example:
             *
             * auto file = ENFORCE(fopen("file.txt", "r"), "Can't find file.txt");
             * auto p = ENFORCE(dynamic_cast<Type*>(pointer));
             *
             * In case the expression is nonzero, the message, if any, is not
             * evaluated so you can plant in there a costly string concatenation:
             *
             * string fn = "file.txt"
             * auto file = ENFORCE(fopen(fn.c_str(), "r"), "Can't find " + fn);
             *
             * The ENFORCE macro stores the file, name, and function into the
             * FBException thrown.
             */
#define DWIO_ENFORCE_CUSTOM(                            \
    exception, expression, errorSource, errorCode, ...) \
  ({                                                    \
    auto const& _tmp = (expression);                    \
    _tmp ? _tmp                                         \
         : throw exception(                             \
               __FILE__,                                \
               __LINE__,                                \
               __FUNCTION__,                            \
               #expression,                             \
               ::melon::to<std::string>(__VA_ARGS__),   \
               errorSource,                             \
               errorCode);                              \
  })

            /*
            Unconditionally throws an exception derived from PolluxException
            containing information about the file, line, and function where it happened.
             */
#define DWIO_EXCEPTION_CUSTOM(exception, errorSource, errorCode, ...) \
  (throw exception(                                                   \
      __FILE__,                                                       \
      __LINE__,                                                       \
      __FUNCTION__,                                                   \
      "",                                                             \
      ::melon::to<std::string>(__VA_ARGS__),                          \
      errorSource,                                                    \
      errorCode))

#define DWIO_RAISE(...)                                          \
  DWIO_EXCEPTION_CUSTOM(                                         \
      kumo::pollux::dwio::common::exception::LoggedException, \
      ::kumo::pollux::error_source::kErrorSourceRuntime,      \
      ::kumo::pollux::error_code::kUnknown,                   \
      ##__VA_ARGS__)

#define DWIO_ENSURE(expr, ...)                                   \
  DWIO_ENFORCE_CUSTOM(                                           \
      kumo::pollux::dwio::common::exception::LoggedException, \
      expr,                                                      \
      ::kumo::pollux::error_source::kErrorSourceRuntime,      \
      ::kumo::pollux::error_code::kUnknown,                   \
      ##__VA_ARGS__)

#define DWIO_ENSURE_NOT_NULL(p, ...) \
  DWIO_ENSURE(p != nullptr, "[Null pointer]: ", ##__VA_ARGS__);

#define DWIO_ENSURE_NE(l, r, ...)         \
  DWIO_ENSURE(                            \
      l != r,                             \
      "[Equality Constraint Violation: ", \
      l,                                  \
      "!=",                               \
      r,                                  \
      "]: ",                              \
      ##__VA_ARGS__);

#define DWIO_ENSURE_EQ(l, r, ...)         \
  DWIO_ENSURE(                            \
      l == r,                             \
      "[Equality Constraint Violation: ", \
      l,                                  \
      "==",                               \
      r,                                  \
      "]: ",                              \
      ##__VA_ARGS__);

#define DWIO_ENSURE_LT(l, r, ...)      \
  DWIO_ENSURE(                         \
      l < r,                           \
      "[Range Constraint Violation: ", \
      l,                               \
      "<",                             \
      r,                               \
      "]: ",                           \
      ##__VA_ARGS__);

#define DWIO_ENSURE_LE(l, r, ...)      \
  DWIO_ENSURE(                         \
      l <= r,                          \
      "[Range Constraint Violation: ", \
      l,                               \
      "<=",                            \
      r,                               \
      "]: ",                           \
      ##__VA_ARGS__);

#define DWIO_ENSURE_GT(l, r, ...)      \
  DWIO_ENSURE(                         \
      l > r,                           \
      "[Range Constraint Violation: ", \
      l,                               \
      ">",                             \
      r,                               \
      "]: ",                           \
      ##__VA_ARGS__);

#define DWIO_ENSURE_GE(l, r, ...)      \
  DWIO_ENSURE(                         \
      l >= r,                          \
      "[Range Constraint Violation: ", \
      l,                               \
      ">=",                            \
      r,                               \
      "]: ",                           \
      ##__VA_ARGS__);
        } // namespace dwio
    } // namespace pollux
} // namespace kumo
